Apple, the Apple logo, AppleScript, Bento, Macintosh, QuickTime, and OpenDoc are
registered trademarks of Apple Computer, Inc.
Finder, Mac, and QuickDraw are trademarks of Apple Computer, Inc.
SOM, SOMObjects, and System Object Model are licensed trademarks of IBM Corporation.
Changes since DR4
1) Added discussion of the clone kind to use when performing a drop.
Changes since DR3
1) Added discussion of an embedded frame's in-limbo flag.
Changes since DR2
1) Added kODPropMouseDownOffset.
2) Fixed refcounting method names.
3) Fixed parameter passing errors with SetValue and GetValue.
About Drag-and-Drop Recipes
The Drag and Drop protocol provides a direct manipulation alternative to the clipboard for copying and moving data between parts in a document, between documents and between documents and the desktop.
This document contains a number of recipes for transferring data through the OpenDoc Drag-and-Drop mechanism. Refer to the Class Documentation for a description of individual methods.
The examples use exception handling in the form of TRY…CATCH_ALL…ENDTRY blocks to catch exceptions, and assumes that errors returned by SOM methods are automatically thrown upon return.
WARNING: These recipes are not actual code, and may contain errors. They serve as examples of how to use the OpenDoc APIs to accomplish specific objectives in particular circumstances. Actual part code usually needs to draw from several recipes to handle the variety of conditions that occur in a real application.
The Drag-and-Drop Focus (or the lack of it)
There is no Drag-and-Drop focus because only one part can be initiating a drop at a time and the drag is synchronous.
Drag and Drop Clone Kinds
When performing a drop, a part must use the clone kind kODCloneDropCopy or kODCloneDropMove in its call to BeginClone. It should not use the clipboard clone kind kODClonePaste. The drag attributes determine which clone kind to use; see the example below for details.
Undo of a Drag-and-Drop
A copy or move using Drag-and-Drop should be undoable. An undo transaction should be started by the part initiating the drag; the part receiving the drop may add undo actions to the transaction. The transaction should be ended after the drop completed by the part initiating the drag. See the Undo Recipes document in the Edit Menu folder for details.
Embedded Frames and their In-Limbo flag
When an embedded frame is dragged or dropped, parts must set the in-limbo flag of the frame correctly. Parts are also responsible for setting the in-limbo flag when a drag and drop operation is undone, redone, or commited via their DisposeActionState method. The code examples here demonstrate the proper manipulation of this flag during the drag and drop, but do not cover how your part must support the in-limbo flag in your UndoAction, RedoAction, or DisposeActionState methods. The complete recipes for setting this flag, and for the actions to take in DisposeActionState, are discussed in the OpenDoc Programmer's Guide.
Recipes
Preparing for a Drop:
A part which can receive a drop should call IsDroppable on all its display frames which can accept a drop. This can be done during ODPart::DisplayFrameAdded or ODPart::DisplayFrameConnected.
If your part does not call SetDroppable on a display frame, the part will not receive any DragEnter, DragWithin, DragLeave and Drop from any facet on that frame.
If your part no longer wishes to receive drops from a particular frame, it can call ODFrame::SetDroppable with kODFalse.
Detecting a Drag (Handling Mouse-Down Events):
Note that a drag can be initiated by any part even when the part does not have any display frame that is set to receive drops.
A drag is initiated by a mouse-down event — kODEvtMouseDown or kODEvtMouseDownEmbedded — received by your HandleEvent method. (For more information on handling events, see the Basic Event Handling recipe.) When handling a mouse-down event, check to see if the coordinates of the mouse-down in the facet are within an item that can be dragged, such as a content item, a text selection, or a selected or bundled embedded frame.
If you’ve determined that the mouse-down is on something draggable, you need to wait and see if the user actually starts dragging, or just releases the mouse without moving it (a click.) On the Macintosh, call the Drag Manager’s WaitMouseMoved routine. This takes no parameters and returns a value of kODTrue if the mouse has moved more than two pixels and a drag is about to begin, or kODFalse if the user released the mouse button without moving the mouse more than two pixels. In the latter case, you should treat the event as just a regular click.
The above discussion is actually slightly simplified: this is what applies in the usual case where the facet is in an active window. Handling a mouse-down in an inactive window requires a bit more work. The OpenDoc Human Interface Guidelines state that a click in an inactive window should just activate the window; the user does not “click through” to the content. However, the user can drag an item from an inactive window without activating the window. That last bit is important: if the window activated, it might cover up the very place in another window that you wanted to drag the item to. (Those who used a Mac before System 7 will recall how much fun this was.)
On the Mac this breaks down into two cases, one where the entire document (process, layer) is inactive, and one where it’s active but the particular window is inactive. These cases look the same to the user, but the Macintosh’s layer model makes them different to implement.
If the document’s process/layer is active, you receive a normal kODEvtMouseDown or kODEvtMouseDownEmbedded event. The logic to handle these looks like this:
if the mouse is over a draggable item and WaitMouseMoved() returns true
Initiate a drag (see Initiating a drag)
else if the window is active
handle the click normally (perhaps starting a drag-selection)
else
activate the window
return kODTrue
In the case where the document’s process/layer is inactive, you receive a different event, either kODEvtBGMouseDown or kODEvtBGMouseDownEmbedded. These have to be handled somewhat differently:
if the mouse is over a draggable item and WaitMouseMoved() returns true
Initiate a drag (see Initiating a drag)
return kODTrue;
else
return kODFalse;
Note that no attempt is made to handle anything other than a drag (in particular the window is not activated) and that you have to return kODFalse if there was no drag. This is so that OpenDoc can tell the Process Manager whether or not the document process needs to be activated after your handler returns.
Warning: It’s important not to do anything but initiate a drag when handling a BGMouseDown event. At the time you’re sent the event, the process is in a Process Manager callback similar to a drag handler, and the same rules apply: interacting with the user by putting up a dialog, or switching processes, will hang or crash the system.
Initiating a drag:
Once a part knows that a drag is to be initiated, the source part should call ODSession's GetDragAndDrop to get the ODDragAndDrop object. Then it should call ODDragAndDrop's Clear and ODDragAndDrop's GetContentStorageUnit to get the ODStorageUnit to where data of the dragged object(s) is copied. Then it can initiate drag by calling ODDragAndDrop's StartDrag. An image for dragging feedback is provided by the source part.
Drag Outline:
On the Macintosh, the imageType is kODDragImageRegionHandle and the image data is a byte array containing the handle (not the content of the handle) of the drag region in global coordinates. It is the part's responsiblity to build this drag region.
Ref con for StartDrag:
The ODEventData pointer is passed in as the refCon. This information is important for interoperating with the Macintosh Drag Manager.
Dragging a frame:
In general, it is a bad idea to drag a frame into itself. Therefore, if you are dragging a frame or a set of frames, you should call ODFrame::SetDragging using kODTrue. This will tell OpenDoc Drag-and-Drop that these frames are not supposed to accept this drop.
Mouse down offset:
The source part should write out the mouse down offset from the top-left corner of the selectuib. This enables the destination part to place the selection at the correct offset from the mouse up position when it is dropped.
// If you have set any frame to not accept any drop, unset it.
frame->SetDragging(ev, kODFalse);
// If the drag was not a move, dragged frames are no longer in limbo
if ( dropResult != kODDropMove )
{
frame->SetInLimbo(ev, kODFalse);
}
if ( dropResult == kODDropMove )
{
// If a frame or set of frames was moved, old facets must be removed.
// The source part must take into account that a moved frame may now be embedded in another part,
// and may have new facets in its new containing frame.
// The source part cannot use the moved frame's facet iterator.
// It must instead use the containing facet's iterator to identify the facets to be deleted.
...
}
Tracking a drag:
Entering a Part’s facet:
ODPart's DragEnter is called when the mouse enters a facet. The part should examine the available data types of the dragged items using the ODDragItemIterator passed in. If the part can handle a drop of the dragged object, it should provide the appropriate feedback (e.g., adorning the droppable frame, changing the cursor). If the destination part cannot handle the data types, nothing should be done.
If there is more than one drag item, the Part should make sure that it can accept all the drag items. If there is one or more drag item that the Part cannot accept, it should return kODFalse from DragEnter and do no visual feedback.
The following code fragment shows a simple example where a part which can only accepts one kind (kMyKind) determines whether the current drag can be accepted.
if (dragSU->Exists(ev, kODPropContents, kMyKind, 0) == kODFalse)
canAccept = kODFalse;
}
return canAccept;
}
However, checking the kinds is not enough to provide the full feedback according to the Macintosh Drag Manager guidelines. The guidelines dictate that no highlighting should be shown on the frame from which the drag is initiated until the drag has left and returned to the frame. Finder (for system 7.5) provides an excellent example. When you drag a file from the Finder, the window in which the file resides is not highlighted. However, when you drag the file out of the window and back to the window, highlighting is shown.
In order to help part developers implement this, OpenDoc Drag-and-Drop provides two Drag Attributes:
kODDragIsInSourceFrame shows that the drag still hasn't left the source frame. This can be used in place of dragHasLeftSenderWindow of the Drag Manager which can only handle applications.
kODDragIsInSourcePart shows that the drag still hasn't left the source part. There is no Macintosh Drag Manager equivalent of this status code.
ODDragResult MyPartDragEnter(MyPart* somSelf,
Environment* ev,
ODDragItemIterator* dragInfo,
ODFacet* facet,
ODPoint where)
{
// Get the Drag-and-Drop object associated with this drag.
// Note that the the session from dropSU may not be the same as the session of the
// target part.
ODDragAndDrop* dad = dropSU->GetSession(ev)->GetDragAndDrop(ev);
// Return ODDragResult to show whether a Drop can happen in this facet.
return canAccept;
}
Within a Part’s facet:
ODPart's DragWithin is called continuously when the mouse is still in the facet. This allows the part to do any processing desired. One good example is when the frame has several hot spots where objects can be dropped. If the mouse is not over these hot spots, the cursor may need to be changed to reflect that the no dropping can be done there even though it is still in a droppable frame. Again, a ODDragItemIterator is passed in so that the part can examine the availabe data types of the dragged objects.
ODPart's DragWithin also provides a chance for the part to examine the state of the machine. For example, some part may want to find out whether the modifier keys are down or not.
Leaving a Part’s facet:
ODPart's DragLeave is called when the mouse leaves a droppable frame. This allows the part to clean up after a drag within it (e.g., removing adornment on the frame, changing the cursor back to its original form).
Receiving a Drop:
If the mouse is released within a facet, ODPart's Drop is called on the part which owns the facet. The part can then figure out whether it can receive the dragged object using the ODDragItemIterator passed in.
OpenDoc Human Interface Specification defines guidelines for the destination part to decide whether a drop should be a move or a copy. OpenDoc Drag-and-Drop provides this information to the part through the OpenDoc Drag Attributes (not to be confused with the attributes from the Macintosh Drag Manager). The part can get the OpenDoc Drag Attributes through the ODDragAndDrop object. When ODDragAndDrop::GetDragAttributes is called, a ODULong is returned. The value of the OpenDoc Drag Attributes reflects important information about the Drop:
kODDropIsInSourceFrame: The drop happens in the frame where the drag is initiated.
kODDropIsInSourcePart: The drop happens in the part when the drag is initiated.
kODDropIsMove: The drop is a move.
kODDropIsCopy: The drop is a copy.
kODDropIsPasteAs: The destination part should put up the Paste As Dialog and enable the user to take further actions.
The destination part should look for kODPropMouseDownOffset to position the dropped data according to the original mousedown offset.
// Use a clone kind of kODCloneDropMove instead of kODClonePaste.
......
dropResult = kODDropMove;
}
else if (dragAttributes & kODDropIsCopy) {
// This is a copy
// Refer to the following Clipboard recipes:
// Incorporating content from the clipboard
// Embedding a part from the clipboard
// Use a clone kind of kODCloneDropCopy instead of kODClonePaste.
......
dropResult = kODDropCopy;
}
else if (dragAttributes & kODDropIsPasteAs) {
// Refer to the following Clipboard recipe:
// Embedding a Part via the Paste As Dialog
.......
}
// For each frame embedded during the drop, remember its current in-limbo status
// before setting the status to false (not in-limbo)
wasInLimbo = frame->IsInLimbo(ev); // for use during undo
frame->SetInLimbo(ev, kODFalse);
return dropResult;
}
Move, Copy, Paste As:
The destination part can also override a move and make the drop into a copy. However, changing a copy to a move is not allowed.
Returning the Drop Result:
After the destination part has responded to the drop, it needs to notify the source part whether it has accepted the drop as a move or a copy. The following are the predefined values of ODDropResult:
kODDropFail: The drop has failed.
kODDropCopy: The drop is a copy.
kODDropMove: The drop is a move.
kODDropUnfinished: The drop is not finished. This value should never be returned by the destination part. It is only use by OpenDoc when DragAndDrop::StartDrag returns immediately in the case of an asynchronous drag.
This result is returned to the source part (via the system) whether the drop is accepted and what action the source part should take.
Incorporating data from a non-OpenDoc document
When a Part's Drop method is called, it is given an ODDragItemIterator. ODDragItemIterator allows the user to access a collection of Drag Items. As described above, it is the receiver's (or the destination part's) responsibility to iterate through all the Drag Items to find out whether it can receive the Drop.
If the Drop comes from an OpenDoc part, there is only one Drag Item in the collection. If the Drop comes from a non-OpenDoc application (e.g., the Finder), there may be one or more Drag Item. If there is more than one Drag Item, the destination part can only accept the drop if it can accept all the drag items.
If the Drop is initiated in the Finder and the dragged object is a file, the Storage Unit of the Drag Item corresponding to the dragged object will contain a special value type in its Contents Property. The value type is "Apple:OSType:FileType:" followed by the actual file type of the file. For example, if the user drags a 'TEXT' file into a Part, "Apple:OSType:FileType:TEXT" will appear in the Contents Property of the Storage Unit of the Drag Item.
In order to access the file, the Part needs to get the HFSFlavor from the value of type "Apple:OSType:FileType:hfs " in the Contents Property. The struct is defined in Drag.h of the Macintosh Drag Manager as follows:
// You can do whatever you want to the file e.g., incorporating the data into your internal data structure.
......
// Cleanup
ODDisposePtr(ba._buffer);
FSClose(fileRefNum);
}
......
Embedding data from a non-OpenDoc document
Even when the destination part cannot incorporate the data, there may be other parts on the system which can. Following the OpenDoc model, the destination part can then embed the non-OpenDoc file and let another Part Editor handle the data.
From the destination's point of view, the recipe for embedding data from a non-OpenDoc documentan OpenDoc part is no different from embedding an OpenDoc part. It first clones the Content Storage Unit and then it calls GetPart using the cloned Storage Unit. The OpenDoc Binding mechanism will use the corresponding Part Editor to manipulate the content of the cloned Storage Unit.
Here's what the destination part should do in its Drop method: